/*-------------------------------------------------------------------------
 *
 * format_type.c
 *	  "优雅" 显示类型名称。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/format_type.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <ctype.h>

#include "access/htup_details.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/numeric.h"
#include "utils/syscache.h"

static char *fc_printTypmod(const char *fc_typname, int32 fc_typmod, Oid fc_typmodout);


/*
 * SQL 函数：format_type(type_oid, typemod)
 *
 * `type_oid' 来自 pg_type.oid， `typemod' 来自 pg_attribute.atttypmod。该函数将获取类型名称并将其格式化及修饰符转换为规范 SQL 格式，如果该类型是标准类型。否则，您只会得到 pg_type.typname 的返回值，若它包含特殊字符或与关键字匹配，则加上双引号。
 *
 * 如果 typemod 为 NULL，则意味着我们在没有可用 typemod 的上下文中格式化类型名称，例如函数参数或结果类型。在某些情况下，这会产生与指定 typemod = -1 略有不同的结果。在 typemod = -1 的情况下，我们感到有必要生成一个输出，解析器将其解释为 typemod -1，以便 pg_dump 生成恢复原始状态的 CREATE TABLE 命令。但是，在 typemod 为 NULL 的情况下，我们假定解析器对 typemod 的解释无关紧要，因此我们愿意输出该类型的略显 "更美观" 的表示。例如，type = bpchar 且 typemod = NULL 将返回 "character"，而 typemod = -1 则返回 "bpchar" --- 前者将被解析器解释为 character(1)，这不产生 typemod -1。
 *
 * XXX 在 typemod = NULL 中编码含义不雅；将其分为一个和两个参数的两个函数会更简洁。然而，现在更改已不值得。
 */
Datum format_type(PG_FUNCTION_ARGS)
{
	Oid			fc_type_oid;
	int32		fc_typemod;
	char	   *fc_result;
	bits16		fc_flags = FORMAT_TYPE_ALLOW_INVALID;

	/* 由于此函数不是严格的，我们必须测试 null 参数 */
	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();

	fc_type_oid = PG_GETARG_OID(0);

	if (PG_ARGISNULL(1))
		fc_typemod = -1;
	else
	{
		fc_typemod = PG_GETARG_INT32(1);
		fc_flags |= FORMAT_TYPE_TYPEMOD_GIVEN;
	}

	fc_result = format_type_extended(fc_type_oid, fc_typemod, fc_flags);

	PG_RETURN_TEXT_P(cstring_to_text(fc_result));
}

/*
 * format_type_extended
 *		生成一个可能有限定条件的类型名称。
 *
 * 默认行为是仅在类型不在搜索路径中时进行限定，忽略给定的 typmod，并且如果给定了不存在的 type_oid，则抛出错误。
 *
 * 'flags' 中的以下位修改行为：
 * - FORMAT_TYPE_TYPEMOD_GIVEN
 *			在输出中包含 typmod（typmod 仍然可以是 -1）
 * - FORMAT_TYPE_ALLOW_INVALID
 *			如果类型 OID 无效或未知，则返回 ??? 或其他内容，而不是失败
 * - FORMAT_TYPE_INVALID_AS_NULL
 *			如果类型 OID 无效或未知，则返回 NULL，而不是 ??? 或其他内容
 * - FORMAT_TYPE_FORCE_QUALIFY
 *			始终对类型名称进行 schema 限定，而不管 search_path
 *
 * 请注意 TYPEMOD_GIVEN 与 "typemod == -1" 是不可互换的；
 * 有关 format_type() 的评论请参见上面。
 *
 * 返回一个 palloc'd 字符串，或 NULL。
 */
char * format_type_extended(Oid fc_type_oid, int32 fc_typemod, bits16 fc_flags)
{
	HeapTuple	fc_tuple;
	Form_pg_type fc_typeform;
	Oid			fc_array_base_type;
	bool		fc_is_array;
	char	   *fc_buf;
	bool		fc_with_typemod;

	if (fc_type_oid == InvalidOid)
	{
		if ((fc_flags & FORMAT_TYPE_INVALID_AS_NULL) != 0)
			return NULL;
		else if ((fc_flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
			return pstrdup("-");
	}

	fc_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_type_oid));
	if (!HeapTupleIsValid(fc_tuple))
	{
		if ((fc_flags & FORMAT_TYPE_INVALID_AS_NULL) != 0)
			return NULL;
		else if ((fc_flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
			return pstrdup("???");
		else
			elog(ERROR, "cache lookup failed for type %u", fc_type_oid);
	}
	fc_typeform = (Form_pg_type) GETSTRUCT(fc_tuple);

	/*
	 * 检查它是否是 "真正的" 数组类型。诸如 "name" 之类的伪数组类型不应被解构。还检查 toast 属性，并且不要解构 "plain storage" 数组类型 --- 这是因为我们不想将 oidvector 显示为 oid[]。
	 */
	fc_array_base_type = fc_typeform->typelem;

	if (IsTrueArrayType(fc_typeform) &&
		fc_typeform->typstorage != TYPSTORAGE_PLAIN)
	{
		/* 切换注意力到数组元素类型 */
		ReleaseSysCache(fc_tuple);
		fc_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_array_base_type));
		if (!HeapTupleIsValid(fc_tuple))
		{
			if ((fc_flags & FORMAT_TYPE_INVALID_AS_NULL) != 0)
				return NULL;
			else if ((fc_flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
				return pstrdup("???[]");
			else
				elog(ERROR, "cache lookup failed for type %u", fc_type_oid);
		}
		fc_typeform = (Form_pg_type) GETSTRUCT(fc_tuple);
		fc_type_oid = fc_array_base_type;
		fc_is_array = true;
	}
	else
		fc_is_array = false;

	fc_with_typemod = (fc_flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0 && (fc_typemod >= 0);

	/*
	 * 查看我们是否想为某些内置类型特殊处理输出。
	 * 请注意，这些特殊情况都应对应于 gram.y 中的特殊产生，以确保类型名称将被视为系统类型，而不是同名的用户类型。
	 *
	 * 如果我们不在这里提供特殊案例输出，类型名称将以用户类型名称的相同方式处理 --- 特别地，如果它与任何词法分析器关键字匹配，它将被加上双引号。这种行为在某些情况下是必需的，例如类型 "bit" 和 "char"。
	 */
	fc_buf = NULL;					/* 无特例的标志 */

	switch (fc_type_oid)
	{
		case BITOID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("bit", fc_typemod, fc_typeform->typmodout);
			else if ((fc_flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0)
			{
				/*
				 * typmod 为 -1 的 bit 与 BIT 不同，后者根据 SQL 规范表示
				 * BIT(1)。将其作为带引号的类型名称报告，以便解析器不会分配虚假的 typmod。
				 */
			}
			else
				fc_buf = pstrdup("bit");
			break;

		case BOOLOID:
			fc_buf = pstrdup("boolean");
			break;

		case BPCHAROID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("character", fc_typemod, fc_typeform->typmodout);
			else if ((fc_flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0)
			{
				/*
				 * typmod 为 -1 的 bpchar 与 CHARACTER 不同，后者根据 SQL 规范表示
				 * CHARACTER(1)。将其报告为 bpchar，以便解析器不会分配虚假的 typmod。
				 */
			}
			else
				fc_buf = pstrdup("character");
			break;

		case FLOAT4OID:
			fc_buf = pstrdup("real");
			break;

		case FLOAT8OID:
			fc_buf = pstrdup("double precision");
			break;

		case INT2OID:
			fc_buf = pstrdup("smallint");
			break;

		case INT4OID:
			fc_buf = pstrdup("integer");
			break;

		case INT8OID:
			fc_buf = pstrdup("bigint");
			break;

		case NUMERICOID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("numeric", fc_typemod, fc_typeform->typmodout);
			else
				fc_buf = pstrdup("numeric");
			break;

		case INTERVALOID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("interval", fc_typemod, fc_typeform->typmodout);
			else
				fc_buf = pstrdup("interval");
			break;

		case TIMEOID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("time", fc_typemod, fc_typeform->typmodout);
			else
				fc_buf = pstrdup("time without time zone");
			break;

		case TIMETZOID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("time", fc_typemod, fc_typeform->typmodout);
			else
				fc_buf = pstrdup("time with time zone");
			break;

		case TIMESTAMPOID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("timestamp", fc_typemod, fc_typeform->typmodout);
			else
				fc_buf = pstrdup("timestamp without time zone");
			break;

		case TIMESTAMPTZOID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("timestamp", fc_typemod, fc_typeform->typmodout);
			else
				fc_buf = pstrdup("timestamp with time zone");
			break;

		case VARBITOID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("bit varying", fc_typemod, fc_typeform->typmodout);
			else
				fc_buf = pstrdup("bit varying");
			break;

		case VARCHAROID:
			if (fc_with_typemod)
				fc_buf = fc_printTypmod("character varying", fc_typemod, fc_typeform->typmodout);
			else
				fc_buf = pstrdup("character varying");
			break;
	}

	if (fc_buf == NULL)
	{
		
/*
		 * 默认处理：报告名称如目录中所示。
		 * 在这里，如果名称在搜索路径中不可见或调用者请求它，我们必须限定名称；如果它不是标准标识符或与任何关键字匹配，我们必须用双引号括起来。
		 */
		char	   *fc_nspname;
		char	   *fc_typname;

		if ((fc_flags & FORMAT_TYPE_FORCE_QUALIFY) == 0 &&
			TypeIsVisible(fc_type_oid))
			fc_nspname = NULL;
		else
			fc_nspname = get_namespace_name_or_temp(fc_typeform->typnamespace);

		fc_typname = NameStr(fc_typeform->typname);

		fc_buf = quote_qualified_identifier(fc_nspname, fc_typname);

		if (fc_with_typemod)
			fc_buf = fc_printTypmod(fc_buf, fc_typemod, fc_typeform->typmodout);
	}

	if (fc_is_array)
		fc_buf = psprintf("%s[]", fc_buf);

	ReleaseSysCache(fc_tuple);

	return fc_buf;
}

/*
 * 该版本用于后端中的错误信息等。
 * 一个区别是，对于无效类型，它将失败。
 *
 * 结果始终是一个palloc'd字符串。
 */
char * format_type_be(Oid fc_type_oid)
{
	return format_type_extended(fc_type_oid, -1, 0);
}

/*
 * 该版本返回一个始终带有限定名的名称（除非它是某些SQL关键字类型名称，例如带有时区的时间戳）。
 */
char * format_type_be_qualified(Oid fc_type_oid)
{
	return format_type_extended(fc_type_oid, -1, FORMAT_TYPE_FORCE_QUALIFY);
}

/*
 * 该版本允许指定非默认的typemod。
 */
char * format_type_with_typemod(Oid fc_type_oid, int32 fc_typemod)
{
	return format_type_extended(fc_type_oid, fc_typemod, FORMAT_TYPE_TYPEMOD_GIVEN);
}

/*
 * 向基本类型名称添加typmod装饰
 */
static char * fc_printTypmod(const char *fc_typname, int32 fc_typmod, Oid fc_typmodout)
{
	char	   *fc_res;

	/* 如果typmod为-1，则不应调用 */
	Assert(fc_typmod >= 0);

	if (fc_typmodout == InvalidOid)
	{
		/* 默认行为：仅打印带括号的整数typmod */
		fc_res = psprintf("%s(%d)", fc_typname, (int) fc_typmod);
	}
	else
	{
		/* 使用特定类型的typmodout过程 */
		char	   *fc_tmstr;

		fc_tmstr = DatumGetCString(OidFunctionCall1(fc_typmodout,
												 Int32GetDatum(fc_typmod)));
		fc_res = psprintf("%s%s", fc_typname, fc_tmstr);
	}

	return fc_res;
}


/*
 * type_maximum_size --- 确定可变宽列的最大宽度
 *
 * 如果最大宽度不确定，则返回-1。特别地，对于此例程未了解的任何类型，我们返回-1。我们假定调用者已经确定该类型是可变宽类型，因此在这里不必查找类型的pg_type元组。
 *
 * 这可能看起来与format_type()无关，但实际上这两个例程共享对不同类型的typmod编码的知识，因此将它们放在一起是方便的。（XXX 现在大部分知识已经从format_type推到typmodout函数中，值得思考是否也值得尝试将这段代码分离出来……）
 */
int32 type_maximum_size(Oid fc_type_oid, int32 fc_typemod)
{
	if (fc_typemod < 0)
		return -1;

	switch (fc_type_oid)
	{
		case BPCHAROID:
		case VARCHAROID:
			/* typemod包括varlena头 */

			/* typemod以字符为单位，而不是字节 */
			return (fc_typemod - VARHDRSZ) *
				pg_encoding_max_length(GetDatabaseEncoding())
				+ VARHDRSZ;

		case NUMERICOID:
			return numeric_maximum_size(fc_typemod);

		case VARBITOID:
		case BITOID:
			/* typemod是（最大）位数 */
			return (fc_typemod + (BITS_PER_BYTE - 1)) / BITS_PER_BYTE
				+ 2 * sizeof(int32);
	}

	/* 未知类型，或无限宽类型，如'text' */
	return -1;
}


/*
 * oidvectortypes - 将类型OID的向量转换为“typname”列表
 */
Datum oidvectortypes(PG_FUNCTION_ARGS)
{
	oidvector  *fc_oidArray = (oidvector *) PG_GETARG_POINTER(0);
	char	   *fc_result;
	int			fc_numargs = fc_oidArray->dim1;
	int			fc_num;
	size_t		fc_total;
	size_t		fc_left;

	fc_total = 20 * fc_numargs + 1;
	fc_result = palloc(fc_total);
	fc_result[0] = '\0';
	fc_left = fc_total - 1;

	for (fc_num = 0; fc_num < fc_numargs; fc_num++)
	{
		char	   *fc_typename = format_type_extended(fc_oidArray->values[fc_num], -1,
													FORMAT_TYPE_ALLOW_INVALID);
		size_t		fc_slen = strlen(fc_typename);

		if (fc_left < (fc_slen + 2))
		{
			fc_total += fc_slen + 2;
			fc_result = repalloc(fc_result, fc_total);
			fc_left += fc_slen + 2;
		}

		if (fc_num > 0)
		{
			strcat(fc_result, ", ");
			fc_left -= 2;
		}
		strcat(fc_result, fc_typename);
		fc_left -= fc_slen;
	}

	PG_RETURN_TEXT_P(cstring_to_text(fc_result));
}
